package com.apobates.jforum2.troll.accounts.controller;

import com.apobates.jforum2.troll.accounts.MemberProfileBean;
import com.apobates.jforum2.troll.accounts.biz.MemberActionDescriptor;
import com.apobates.jforum2.troll.accounts.biz.MemberActiveRecordsService;
import com.apobates.jforum2.troll.accounts.biz.MemberOnlineService;
import com.apobates.jforum2.troll.accounts.biz.MemberService;
import com.apobates.jforum2.troll.accounts.biz.MemberSocialInfoService;
import com.apobates.jforum2.troll.accounts.entity.Member;
import com.apobates.jforum2.troll.accounts.entity.MemberActiveRecords;
import com.apobates.jforum2.troll.accounts.entity.MemberSocialInfo;
import com.apobates.jforum2.troll.accounts.entity.MemberOnline;
import com.apobates.jforum2.troll.accounts.exception.MemberExistException;
import com.apobates.jforum2.troll.accounts.mapper.MemberOnlineVoTypeAdapter;
import com.apobates.jforum2.troll.accounts.mapper.MemberProfileVoMapperEntity;
import com.apobates.jforum2.troll.accounts.mapper.MemberRecordVoMapperEntity;
import com.apobates.jforum2.troll.accounts.mapper.MemberSocialVoMapperEntity;
import com.apobates.jforum2.troll.accounts.mapper.MemberVoMapperEntity;
import com.apobates.jforum2.troll.accounts.stub.CommonResponseVo;
import com.apobates.jforum2.troll.accounts.stub.vo.MemberOnlineVo;
import com.apobates.jforum2.troll.accounts.stub.vo.MemberProfileVo;
import com.apobates.jforum2.troll.accounts.stub.vo.MemberRecordVo;
import com.apobates.jforum2.troll.accounts.stub.vo.MemberSocialVo;
import com.apobates.jforum2.troll.accounts.stub.vo.MemberStatsCollectVo;
import com.apobates.jforum2.troll.accounts.stub.vo.MemberVo;
import com.apobates.jforum2.troll.attention.ImageDirectoryEnum;
import com.apobates.jforum2.troll.attention.ImageStoreDomain;
import com.apobates.jforum2.troll.attention.core.AvatarImagePathConvert;
import com.apobates.jforum2.troll.regular.ForumActionEnum;
import com.apobates.jforum2.troll.regular.ImageIOMeta;
import com.apobates.jforum2.troll.regular.MemberGroupEnum;
import com.apobates.jforum2.troll.utils.core.Commons;
import com.apobates.jforum2.troll.utils.core.DateTimeUtils;
import com.apobates.jforum2.troll.utils.core.ResponseResult;
import com.apobates.jforum2.troll.utils.core.api.IpLocation;
import com.apobates.jforum2.troll.utils.lang.CommonBean;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import java.lang.reflect.Type;
import java.net.URL;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
import java.util.Set;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
 * 会员Api
 *
 * @author xiaofanku
 * @since 20201107
 */
@RestController
@RequestMapping("/member")
public class MemberController {
    @Autowired
    private MemberService memberService;
    @Autowired
    private MemberOnlineService memberOnlineService;
    @Autowired
    private MemberSocialInfoService memberSocialInfoService;
    @Autowired
    private MemberActiveRecordsService memberActiveRecordsService;
    @Value("${site.dec.salt}")
    private String globalDecSalt;
    @Value("${site.member.invite}")
    private boolean activeInviteCode;
    @Value("${img.bucket.domain}")
    private String imageBucketDomain;
    @Value("${img.bucket.upload.direct}")
    private String imageBucketUploadDirect;
    private final static Logger logger = LoggerFactory.getLogger(MemberController.class);

    //测试配置中心
    @GetMapping("/vari")
    public String getGDecSalt(){
        return this.globalDecSalt;
    }
    @GetMapping("/vari/bol")
    public String getActiveInvite(){
        return this.activeInviteCode?"1":"0";
    }
    //1
    @GetMapping("/{id}")
    public ResponseEntity<MemberVo> getMember(@PathVariable("id") long memberId) {
        Supplier<Optional<Member>> rs = ()->(memberId>0)?memberService.get(memberId):Optional.empty();
        ResponseResult<MemberVo> rr = ResponseResult.ofEntityVo(rs, new MemberVoMapperEntity());
        return ResponseEntity.status(rr.getCode()).contentType(MediaType.APPLICATION_JSON).body(rr.getEntity());
    }
    //2:个人头像
    @GetMapping("/{id}/avatar")
    public ResponseEntity<String> getMemberAvatar(@PathVariable("id") long memberId){
        //什么情况下是empty?1)会员不存在;2)会员存在但未设置
        Optional<String> avatar = Optional.empty();
        // 让客户端去调解码服务
        try {
            avatar = memberService.getMemberAvatar(memberId).map(ava->{
                return AvatarImagePathConvert.getDefaultAvtarFilePath(ava);
            });
        }catch (MemberExistException e){
            if(logger.isDebugEnabled()){
                logger.debug("[Args]member#"+memberId+" is "+e.getMessage());
            }
        }
        //
        ResponseResult<String> rr = ResponseResult.Helper.ofOptional(avatar);
        return ResponseEntity.status(rr.getCode()).contentType(MediaType.APPLICATION_JSON).body(rr.getEntity());
    }
    //3:登录
    @PostMapping(value="/checkIn", consumes="application/x-www-form-urlencoded")
    public ResponseEntity<MemberVo> login(
            @RequestParam("names") String names,
            @RequestParam("password") String password,
            @RequestParam("token") String token,
            @RequestParam("ip")String ipAddr){
        Supplier<Optional<Member>> rs = ()->Optional.ofNullable(memberService.signIn(names.trim(), password.trim(), getInstance(ipAddr, token)));
        ResponseResult<MemberVo> rr = ResponseResult.ofEntityVo(rs, new MemberVoMapperEntity());
        return ResponseEntity.status(rr.getCode()).contentType(MediaType.APPLICATION_JSON).body(rr.getEntity());
    }
    //4:注销
    @PostMapping(value="/checkOut", consumes="application/x-www-form-urlencoded")
    public ResponseEntity<String> signout(
            @RequestParam("id") long memberId,
            @RequestParam("names") String names,
            @RequestParam("token") String token,
            @RequestParam("ip")String ipAddr){
        Supplier<Optional<Boolean>> rs = ()->memberService.signOut(memberId, names, getInstance(ipAddr, token));
        ResponseResult<String> rr = ResponseResult.ofBoolean(rs, "用户注销在线状态失败");
        return ResponseEntity.status(rr.getCode()).contentType(MediaType.APPLICATION_JSON).body(rr.getEntity());
    }
    //5:注册
    @PostMapping(value="/registe", consumes="application/x-www-form-urlencoded")
    public ResponseEntity<MemberVo> registe(
            @RequestParam("names") String names,
            @RequestParam("password") String password,
            @RequestParam(value="nickname", required=false, defaultValue="-") String nickname,
            @RequestParam(value="invite", required=false, defaultValue="") String inviteCode,
            @RequestParam("token") String token,
            @RequestParam("ip")String ipAddr){
        logger.error("[account-api]CTL start registe");
        //注册邀请码必填项验证
        //不可用 && 需要邀请码
        if(!Commons.isNotBlank(inviteCode) && activeInviteCode){
            logger.error("[account-api]lost registe inviteCode");
            return ResponseEntity.badRequest().body(new MemberVo());//.body("注册需要的邀请码不可用");
        }
        Supplier<Optional<Member>> rs = () -> Optional.ofNullable(memberService.signUp(names,
                password,
                inviteCode,
                nickname,
                getInstance(ipAddr, token)));
        logger.error("[account-api]outprint registe response start");
        ResponseResult<MemberVo> rr = ResponseResult.ofEntityVo(rs, new MemberVoMapperEntity());
        return ResponseEntity.status(rr.getCode()).contentType(MediaType.APPLICATION_JSON).body(rr.getEntity());
    }
    //校验uid时的
    //ETC
    //6:检查注册邀请码是否可用
    @GetMapping("/invite/{code}")
    public ResponseEntity<CommonResponseVo> getInviteCode(@PathVariable("code") String inviteCode){
        Map<String,String> d = memberService.getActiveInviteCode(inviteCode);
        String id = d.getOrDefault("id", "0");
        String value = d.getOrDefault("code", inviteCode);
        //
        ResponseResult<CommonResponseVo> rr = ResponseResult.Helper.<CommonResponseVo>ofEntity(new CommonResponseVo(id, value));
        return ResponseEntity.status(rr.getCode()).contentType(MediaType.APPLICATION_JSON).body(rr.getEntity());
    }
    //7:检查会员帐号是否可用
    @GetMapping("/detection/{names}")
    public ResponseEntity<String> checkMemberNames(@PathVariable("names") String names){
        Supplier<Optional<Boolean>> resultFun = ()->memberService.checkNamesUnique(names);
        ResponseResult<String> rr = ResponseResult.ofBoolean(resultFun, "查询会员登录帐号失败");
        return ResponseEntity.status(rr.getCode()).contentType(MediaType.APPLICATION_JSON).body(rr.getEntity());
    }
    //8:会员在线记录
    @GetMapping("/online/{member}")
    public ResponseEntity<MemberOnlineVo> getMemberOnlineRecord(@PathVariable("member") long memberId){
        Supplier<Optional<MemberOnline>> rs = ()->memberOnlineService.get(memberId);
        ResponseResult<MemberOnlineVo> rr = ResponseResult.ofEntityVo(rs, new MemberOnlineVoTypeAdapter());
        return ResponseEntity.status(rr.getCode()).contentType(MediaType.APPLICATION_JSON).body(rr.getEntity());
    }
    /*9:统计信息|转移至actionlogs
    @GetMapping("/stats/{member}")
    public ResponseEntity<ThreadsStatsCollectVo> getMemberActionStats(@PathVariable("member") long memberId){
        ThreadsStatsCollectVo tsc = getStats(memberId);
        //
        ResponseResult<ThreadsStatsCollectVo> rr = ResponseResult.ofEntity(tsc);
        return ResponseEntity.status(rr.getCode()).contentType(MediaType.APPLICATION_JSON).body((ThreadsStatsCollectVo)rr.getEntity());
    }*/
    //10:保存在线记录
    @PostMapping(value="/online/create", consumes="application/json; charset=utf-8")
    public ResponseEntity<String> newMemberOnline(@RequestBody MemberOnlineVo mov){
        MemberOnline mo = new MemberOnlineVoTypeAdapter().unmarshal(mov);
        Supplier<Optional<Long>> rs = ()->Optional.ofNullable(memberOnlineService.create(mo));
        ResponseResult<String> rr = ResponseResult.ofLongResult(rs, "在线记录创建失败");
        return ResponseEntity.status(rr.getCode()).contentType(MediaType.APPLICATION_JSON).body(rr.getEntity());
    }
    //11:个人信息|话题统计信息使用form格式
    @PostMapping(value="/{id}/profile/form", consumes="application/x-www-form-urlencoded")
    public ResponseEntity<MemberProfileVo> getMemberProfile(@PathVariable("id") long memberId, @RequestParam("stats")String topicStatsJsonString){
        Type gsonType = new TypeToken<Map<ForumActionEnum,Long>>(){}.getType();
        Map<ForumActionEnum, Long> rawdata = new Gson().fromJson(topicStatsJsonString, gsonType);
        Supplier<Optional<MemberProfileBean>> rs = ()->memberService.calcMemberProfileBean(memberId, rawdata);
        ResponseResult<MemberProfileVo> rr = ResponseResult.ofEntityVo(rs, new MemberProfileVoMapperEntity());
        return ResponseEntity.status(rr.getCode()).contentType(MediaType.APPLICATION_JSON).body(rr.getEntity());
    }
    //11:个人信息|话题统计信息使用Json格式
    @PostMapping(value="/{id}/profile/json", consumes="application/json")
    public ResponseEntity<MemberProfileVo> calcProfileByJson(@PathVariable("id") long memberId, @RequestBody Map<ForumActionEnum, Long> topicAction){
        Supplier<Optional<MemberProfileBean>> rs = ()->memberService.calcMemberProfileBean(memberId, topicAction);
        ResponseResult<MemberProfileVo> rr = ResponseResult.ofEntityVo(rs, new MemberProfileVoMapperEntity());
        return ResponseEntity.status(rr.getCode()).contentType(MediaType.APPLICATION_JSON).body(rr.getEntity());
    }
    
    //12:编辑个人信息
    @PutMapping(value="/{id}/profile/modify", consumes="application/x-www-form-urlencoded")
    public ResponseEntity<String> editMemberProfile(
            @PathVariable("id") long memberId,
            @RequestParam("signature") String signatureContent,
            @RequestParam("nickname") String nickname,
            @RequestParam("token") String token,
            @RequestParam("ip")String ipAddr){
        Supplier<Optional<Boolean>> rs = ()->memberService.edit(memberId,
                signatureContent,
                nickname,
                getInstance(ipAddr, token));
        ResponseResult<String> rr = ResponseResult.ofBoolean(rs, "编辑个人信息失败");
        return ResponseEntity.status(rr.getCode()).contentType(MediaType.APPLICATION_JSON).body(rr.getEntity());
    }
    //13:编辑密码
    @PutMapping(value="/{id}/password/modify", consumes="application/x-www-form-urlencoded")
    public ResponseEntity<String> editMemberPassword(@PathVariable("id") long memberId,
                                      @RequestParam("original") String oldPswd,
                                      @RequestParam("current") String newPswd,
                                      @RequestParam("token") String token,
                                      @RequestParam("ip")String ipAddr){
        Supplier<Optional<Boolean>> rs = ()->memberService.editPswd(memberId, oldPswd, newPswd, getInstance(ipAddr, token));
        ResponseResult<String> rr = ResponseResult.ofBoolean(rs, "更新密码操作失败");
        return ResponseEntity.status(rr.getCode()).contentType(MediaType.APPLICATION_JSON).body(rr.getEntity());
    }
    //14:重置会员密码
    @PutMapping(value="/{id}/password/reset", consumes="application/x-www-form-urlencoded")
    public ResponseEntity<String> resetMemberPassword(@PathVariable("id") long memberId,
                                       @RequestParam("current") String newPswd,
                                       @RequestParam("token") String token,
                                       @RequestParam("ip")String ipAddr){
        Supplier<Optional<Boolean>> rs = ()->memberService.resetPswd(memberId, newPswd, getInstance(ipAddr, token));
        ResponseResult<String> rr = ResponseResult.ofBoolean(rs, "重置会员密码失败");
        return ResponseEntity.status(rr.getCode()).contentType(MediaType.APPLICATION_JSON).body(rr.getEntity());
    }
    //15:编辑头像(先编码->再保存)
    @PutMapping(value="/{id}/avatar/modify", consumes="application/x-www-form-urlencoded")
    public ResponseEntity<String> editMemberAvatar(@PathVariable("id") long memberId,
                                      @RequestParam("avatar") String avtarFileName,
                                      @RequestParam("app_domain") String appLocalDomain,
                                      @RequestParam("app_avatar_direct") String avatarDefaultDirect,
                                      @RequestParam("token") String token,
                                      @RequestParam("ip")String ipAddr){
        logger.error("[account-api]CTL start modify avatar");
        ImageStoreDomain imageStoreDomain = new ImageStoreDomain(){
            //存储路径指向所有应用共用的一个:pic.x
            @Override
            public String getDefaultDomain() {
                return getImageIOMeta().getImageBucketDomain();
            }
            //各个应用可能有不同的域名:pc.x,app.x,mobile.x
            @Override
            public String getLocalDomain() {
                return appLocalDomain;
            }
        };
        //参数avtarpath='/default/xx.png';
        String avatarURL = imageStoreDomain.getDirectoryEnumDomain(ImageDirectoryEnum.LOCAL).orElse(imageStoreDomain.getLocalDomain()) + "/" + avatarDefaultDirect + avtarFileName;
        Optional<String> avatarPath = Optional.empty();
        try {
            avatarPath = new AvatarImagePathConvert(imageStoreDomain, avatarDefaultDirect).encode(new URL(avatarURL), ImageDirectoryEnum.LOCAL);
        }catch ( java.net.MalformedURLException e){
            logger.error("[account-api] avatar process fail, exception: "+e.getMessage());
        }
        // Optional<String> rs = Optional.empty();
        if(avatarPath.isPresent()) {
            logger.error("[account-api] update avatar ");
            boolean r = memberService.updateAvatar(memberId, avatarPath.get(), getInstance(ipAddr, token)).orElse(false);
            if(!r){
                avatarPath = Optional.empty();
                logger.error("[account-api] update avatar fail");
            }
        }
        //
        logger.error("[account-api]outprint avatar update response start");
        ResponseResult<String> rr = ResponseResult.Helper.ofOptional(avatarPath);
        return ResponseEntity.status(rr.getCode()).contentType(MediaType.APPLICATION_JSON).body(rr.getEntity());
    }
    //16:社会化信息
    @GetMapping("/{id}/social")
    public ResponseEntity<MemberSocialVo> getMemberSocial(@PathVariable("id") long memberId){
        Supplier<Optional<MemberSocialInfo>> rs = ()->memberSocialInfoService.getByMember(memberId);
        ResponseResult<MemberSocialVo> rr = ResponseResult.ofEntityVo(rs, new MemberSocialVoMapperEntity());
        return ResponseEntity.status(rr.getCode()).contentType(MediaType.APPLICATION_JSON).body(rr.getEntity());
    }
    //17:查询社会化信息
    @PostMapping(value="/social/query", consumes="application/x-www-form-urlencoded")
    public ResponseEntity<MemberSocialVo> getMemberSocialByMail(@RequestParam("names") String names, @RequestParam("mail") String email){
        Supplier<Optional<MemberSocialInfo>> rs = ()->memberSocialInfoService.getByMember(names, email, globalDecSalt);
        ResponseResult<MemberSocialVo> rr = ResponseResult.ofEntityVo(rs, new MemberSocialVoMapperEntity());
        return ResponseEntity.status(rr.getCode()).contentType(MediaType.APPLICATION_JSON).body(rr.getEntity());
    }
    //18:编辑社会化信息
    @PutMapping(value="/{id}/social/modify", consumes="application/x-www-form-urlencoded")
    public ResponseEntity<String> editMemberSocial(
            @PathVariable("id")long memberId,
            @RequestParam("names") String names,
            @RequestParam("record") long recordId,
            @RequestParam("weibo") String weibo,
            @RequestParam("weixin") String weixin,
            @RequestParam("qq") String qq,
            @RequestParam("mail") String email,
            @RequestParam("token") String token,
            @RequestParam("ip")String ipAddr){
        Optional<MemberSocialInfo> r = memberSocialInfoService.edit(
                recordId,
                memberId,
                names,
                "nobaba",
                weibo,
                weixin,
                qq,
                email,
                globalDecSalt, getInstance(ipAddr, token));
        //
        ResponseResult<String> rr = ResponseResult.Helper.ofBoolean(r.isPresent());
        return ResponseEntity.status(rr.getCode()).contentType(MediaType.APPLICATION_JSON).body(rr.getEntity());
    }
    //19:新增社会化信息
    @PostMapping(value="/{id}/social/create", consumes="application/x-www-form-urlencoded")
    public ResponseEntity<MemberSocialVo> newMemberSocial(
            @PathVariable("id")long memberId,
            @RequestParam("names") String names,
            @RequestParam("weibo") String weibo,
            @RequestParam("weixin") String weixin,
            @RequestParam("qq") String qq,
            @RequestParam("mail") String email,
            @RequestParam("token") String token,
            @RequestParam("ip")String ipAddr){
        Supplier<Optional<MemberSocialInfo>> rs = ()->memberSocialInfoService.create(
                memberId,
                names,
                "nobaba",
                weibo,
                weixin,
                qq,
                email,
                globalDecSalt, getInstance(ipAddr, token));
        //
        ResponseResult<MemberSocialVo> rr = ResponseResult.ofEntityVo(rs, new MemberSocialVoMapperEntity());
        return ResponseEntity.status(rr.getCode()).contentType(MediaType.APPLICATION_JSON).body(rr.getEntity());
    }
    /*20:社区标兵|转移至actionlogs
    @GetMapping("/model")
    public List<MemberModelVo> getBrandMemberModel(@RequestParam(value="size", required=false, defaultValue="10") int showSize){
        Map<Long, Map<String, Long>> rs = topicActionCollectionService.orderMemberTopices(showSize);
        Function<Map.Entry<Long, Map<String, Long>>, MemberModelVo> coverMapper = entry->{
            long mid = entry.getKey();
            return entry.getValue().entrySet().stream().map(tmp->new MemberModelVo(mid, tmp.getValue(), tmp.getKey())).findFirst().get();
        };
        Stream<MemberModelVo> data = rs.entrySet().stream().map(coverMapper);
        return data.sorted(Comparator.comparing(MemberModelVo::getValue).reversed()).collect(Collectors.toList());
    }*/
    //21:底部的会员统计汇总
    @GetMapping("/collect")
    public ResponseEntity<MemberStatsCollectVo> getBoardStatsCollect(@RequestParam(value="cycle", required=false, defaultValue="30") int statsCycle){
        long members=0L;
        try {
            members = memberService.count(); //注册人数
        } catch (NullPointerException e) {}
        //最近注册的会员
        CommonBean recent = null;
        try {
            recent = memberService
                    .getRecent(1)
                    .map(m -> new CommonBean(m.getId(), m.getNickname()))
                    .findAny()
                    .orElse(new CommonBean(MemberGroupEnum.GUEST.getSymbol(), MemberGroupEnum.GUEST.getTitle()));
            //最近注册的会员是谁?
        } catch (NullPointerException e) {}
        long onlines = 0L;
        try {
            LocalDateTime finish = LocalDateTime.now();
            LocalDateTime start = DateTimeUtils.beforeMinuteForDate(finish, 30);
            onlines = memberOnlineService.count(start, finish); //这里不包含游客
        } catch (Exception e) {
            if(logger.isDebugEnabled()){
                logger.debug(e.getMessage(), e);
            }
        }
        MemberStatsCollectVo mscv = new MemberStatsCollectVo(members, recent.getId(), recent.getTitle(), statsCycle+"", onlines);
        //
        ResponseResult<MemberStatsCollectVo> rr = ResponseResult.Helper.<MemberStatsCollectVo>ofEntity(mscv);
        return ResponseEntity.status(rr.getCode()).contentType(MediaType.APPLICATION_JSON).body(rr.getEntity());
    }
    //22:上次登录记录
    @GetMapping("/login/lastrecord/{id}/{names}")
    public ResponseEntity<MemberRecordVo> getLastLoginRecord(@PathVariable("id") long memberId, @PathVariable("names") String names) {
        logger.error("[account-api]CTL start query last active");
        Supplier<Optional<MemberActiveRecords>> rs = () -> memberActiveRecordsService.getPreviousLoginRecord(memberId, names);
        logger.error("[account-api]outprint last active response start");
        ResponseResult<MemberRecordVo> rr = ResponseResult.ofEntityVo(rs, new MemberRecordVoMapperEntity());
        return ResponseEntity.status(rr.getCode()).contentType(MediaType.APPLICATION_JSON).body(rr.getEntity());
    }
    //23:
    @GetMapping("/actionrecord/{names}")
    public List<MemberRecordVo> getActionRecords(@PathVariable("names") String names, @RequestParam("size") int showSize){
        return memberActiveRecordsService.getByMemberByNames(names, showSize).map(MemberRecordVoMapperEntity.mapper()).collect(Collectors.toList());
    }
    /*
    //24:会员的操作记录|转移至actionlogs
    @GetMapping("/{id}/history/{page}/{number}")
    public Response getMemberActionHistory(@PathVariable("id") long memberId, @PathVariable("page") int page, @PathVariable("number") int pageSize){
        Page<TopicActionCollectionReplica> rs = topicActionCollectionService.getByMember(memberId, new PageRequest(page, pageSize));
        return ResponseResult.PageResultBuilder.<ThreadsRecordVo,TopicActionCollectionReplica>getInstance(page, pageSize).from(rs.getTotalElements(), rs.getResult()).build(new TopicActionRecordVoMapperEntity()).toJson();
    }
    //25:会员收藏的话题|转移至actionlogs
    @GetMapping("/{id}/topic/favorite/{page}/{number}")
    public Response getMemberFavoriteTopic(@PathVariable("id") long memberId, @PathVariable("page") int page, @PathVariable("number") int pageSize){
        Page<TopicActionCollectionReplica> rs = topicActionCollectionService.getAllByMemberAction(memberId, ForumActionEnum.TOPIC_FAVORITE, new PageRequest(page, pageSize));
        return ResponseResult.PageResultBuilder.<ThreadsRecordVo,TopicActionCollectionReplica>getInstance(page, pageSize).from(rs.getTotalElements(), rs.getResult()).build(new TopicActionRecordVoMapperEntity()).toJson();
    }
    //26:会员点赞的话题|转移至actionlogs
    @GetMapping("/{id}/topic/like/{page}/{number}")
    public Response getMemberLickTopic(@PathVariable("id") long memberId, @PathVariable("page") int page, @PathVariable("number") int pageSize){
        Page<TopicActionCollectionReplica> rs = topicActionCollectionService.getAllByMemberAction(memberId, ForumActionEnum.TOPIC_LIKED, new PageRequest(page, pageSize));
        return ResponseResult.PageResultBuilder.<ThreadsRecordVo,TopicActionCollectionReplica>getInstance(page, pageSize).from(rs.getTotalElements(), rs.getResult()).build(new TopicActionRecordVoMapperEntity()).toJson();
    }*/
    //27:获取客户端IP
    @GetMapping("/ip/query")
    public Map<String,String> getClientRequestIp(HttpServletRequest request){
        String ipAddr = Commons.getRemoteAddr(request);
        Map<String,String> data = Map.of("ip", ipAddr);
        return data;
    }
    //28:查看多个会员信息
    @GetMapping("/multi")
    public List<MemberVo> getMultiAccounts(@RequestParam("ids") String memberIdString){
        Set<Long> args = Commons.toLongSet(memberIdString);
        return memberService.queryCollection(args).map(MemberVoMapperEntity.mapper()).collect(Collectors.toList());
    }
    
    private MemberActionDescriptor getInstance(String ipAddr, String token){
        return new MemberActionDescriptor() {
            @Override
            public String getToken() {
                return token;
            }

            @Override
            public IpLocation location() {
                return IpLocation.empty(ipAddr);
            }

            @Override
            public String getReferrer() {
                return "referrer";
            }

            @Override
            public String getDevice() {
                return "device";
            }

            @Override
            public String getAgent() {
                return "agent";
            }
        };
    }
    private ImageIOMeta getImageIOMeta(){
        return new ImageIOMeta(){
            @Override
            public String getImageBucketDomain() {
                return imageBucketDomain;
            }

            @Override
            public String getUploadImageDirectName() {
                return imageBucketUploadDirect;
            }
        };
    }

}
/*
/member/{id}
/member/{id}/profile
/member/{id}/avatar
/member/{id}/profile/modify
/member/{id}/password/modify
/member/{id}/password/reset
/member/{id}/avatar/modify
/member/{id}/social
/member/{id}/social/modify
/member/{id}/social/create
/member/{id}/history/{page}/{number}
/member/{id}/topic/favorite/{page}/{number}
/member/{id}/topic/like/{page}/{number}
/member/model
/member/collect
/member/login/lastrecord/{id}/{names}
/member/actionrecord/{names}
/member/social/query
/member/stats/{member}
/member/online/{member}
/member/online/create
/member/checkIn
/member/checkOut
/member/registe
/member/invite/{code}
/member/detection/{names}
/member/ip/query
* */